package org.anarres.gradle.plugin.velocity; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.List; import java.util.Map; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.runtime.log.SystemLogChute; import org.codehaus.groovy.runtime.DefaultGroovyMethods; import org.gradle.api.GradleException; import org.gradle.api.file.EmptyFileVisitor; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTree; import org.gradle.api.file.FileVisitDetails; import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.SourceTask; import org.gradle.api.tasks.TaskAction; /** * A bare velocity task. * * You may use this to do arbitrary velocity processing without * necessarily applying the plugin. * * @author shevek */ public class VelocityTask extends SourceTask { private static interface Collector { public void accept(@Nonnull File dir); } private static class IncludePathCollector implements Collector { private final StringBuilder out = new StringBuilder(); @Override public void accept(File dir) { if (out.length() > 0) out.append(", "); out.append(dir.getAbsolutePath()); } } private class IncludeFileCollector implements Collector { private FileCollection out = getProject().files(); @Override public void accept(File dir) { out = out.plus(getProject().fileTree(dir)); } } private File outputDir; private List<File> includeDirs; private Map<String, Object> contextValues; @OutputDirectory @Nonnull // Not @Optional public File getOutputDir() { return outputDir; } public void setOutputDir(@Nonnull File outputDir) { this.outputDir = outputDir; } @Input @Optional @CheckForNull public List<File> getIncludeDirs() { return includeDirs; } public void setIncludeDirs(@Nonnull List<File> includeDirs) { this.includeDirs = includeDirs; } @Input @Optional @CheckForNull public Map<String, Object> getContextValues() { return contextValues; } public void setContextValues(@Nonnull Map<String, Object> contextValues) { this.contextValues = contextValues; } private void setProperty(VelocityEngine engine, String name, Object value) { getLogger().info("VelocityEngine property: " + name + " = " + value); engine.setProperty(name, value); } private void collectDir(@Nonnull Collector collector, @Nonnull File dir) { getLogger().info("Collecting dir " + dir); collector.accept(dir); } private void collectDirs(@Nonnull Collector collector, @CheckForNull Iterable<File> dirs) { if (dirs != null) for (File dir : dirs) collectDir(collector, dir); } private void collectUnknown(@Nonnull Collector collector, @Nonnull Iterable<Object> sources) { for (Object source : sources) { getLogger().info("Attepmting to collect " + source.getClass() + ":" + source); if (source instanceof File) collectDir(collector, (File) source); else if (source instanceof SourceDirectorySet) collectDirs(collector, ((SourceDirectorySet) source).getSrcDirs()); // I wish we could introspect CompositeFileTree. } } @InputFiles @Nonnull // Not @Optional private FileCollection getIncludeFiles() { IncludeFileCollector collector = new IncludeFileCollector(); collectUnknown(collector, source); collectDirs(collector, getIncludeDirs()); for (File file : collector.out) getLogger().info("Including " + file); return collector.out; } @TaskAction public void runVelocity() throws Exception { final FileTree inputFiles = getSource(); final File outputDir = getOutputDir(); DefaultGroovyMethods.deleteDir(outputDir); outputDir.mkdirs(); final VelocityEngine engine = new VelocityEngine(); setProperty(engine, VelocityEngine.RUNTIME_LOG_LOGSYSTEM_CLASS, SystemLogChute.class.getName()); setProperty(engine, VelocityEngine.RESOURCE_LOADER, "file"); setProperty(engine, VelocityEngine.FILE_RESOURCE_LOADER_CACHE, "true"); // FILE_RESOURCE_LOADER_PATH actually takes a comma separated list. IncludePathCollector collector = new IncludePathCollector(); collectUnknown(collector, source); collectDirs(collector, getIncludeDirs()); setProperty(engine, VelocityEngine.FILE_RESOURCE_LOADER_PATH, collector.out.toString()); inputFiles.visit(new EmptyFileVisitor() { @Override public void visitFile(FileVisitDetails fvd) { try { File outputFile = fvd.getRelativePath().getFile(outputDir); if (getLogger().isDebugEnabled()) getLogger().debug("Preprocessing " + fvd.getFile() + " -> " + outputFile); VelocityContext context = new VelocityContext(); Map<String, Object> contextValues = getContextValues(); if (contextValues != null) for (Map.Entry<String, Object> e : contextValues.entrySet()) context.put(e.getKey(), e.getValue()); context.put("project", getProject()); context.put("package", DefaultGroovyMethods.join(fvd.getRelativePath().getParent().getSegments(), ".")); context.put("class", fvd.getRelativePath().getLastName().replaceFirst("\\.java$", "")); FileReader reader = new FileReader(fvd.getFile()); try { outputFile.getParentFile().mkdirs(); FileWriter writer = new FileWriter(outputFile); try { engine.evaluate(context, writer, fvd.getRelativePath().toString(), reader); } finally { writer.close(); } } finally { reader.close(); } } catch (IOException e) { throw new GradleException("Failed to process " + fvd, e); } } }); } }